package spinal_loongarch_core132

import spinal.core._
import spinal.lib._

object DCacheConfig extends CacheConfig{
    val w_idx :Int = 8
    val w_pos :Int = 2
    val n_way :Int = 2
    val withBitMask = true
    val withDirty = true
}
class DMiss extends IMiss{
    val occupy :Bool = Bool()
    val arsize :UInt = UInt(3 bits)
    val awvalid:Bool = Bool()
    val awaddr :UInt = LISA.PA
    val awsize :UInt = UInt(3 bits)
    val wdata  :Vec[UInt] = Vec(UInt(32 bits),4)
    val wstrb  :Bits = Bits(4 bits)
    override def asMaster():Unit = {
        super.asMaster()
        out(occupy,awvalid,arsize,awaddr,awsize,wdata,wstrb)
    }
}
class MemType extends Bundle{
    val st = Bool()
    val ld = Bool()
    val pr = Bool()
    val w  = Bool()
    val h  = Bool()
    val b  = Bool()
    def size:UInt = (B("0") ## w ## h).asUInt
}
class DLookupInfo extends LookupInfo{
    val cfg  = DCacheConfig
    val pt   :UInt = UInt(LISA.w_ppn bits)
    val mat  :UInt = UInt(2 bits)
    val ex   :Bool = Bool()      
    val ecode:UInt = LISA.Ex.Code
    val esubc:UInt = LISA.Ex.Subc
    val cached:Bool = mat === U(1)
    val vidx :UInt  = Reg(UInt(12 bits))
    val mem  :MemType = Reg(new MemType)
    val wdata:UInt = Reg(LISA.GPR)
    val wstrb:Bits = Reg(Bits( 4 bits))
    val cacop:Cacop= Reg(new Cacop)
    def wmask:Bits = Range(0,4).map((i:Int)=>Mux(wstrb(i),B"hff",B(0))).reduce(_##_)
    def setFields(miss:DMiss):Unit = {
        miss.araddr := pa
        miss.line   := cached
    }
    def send_ex(e:DExcept){
        e.ecode := ecode
        e.esubc := esubc
    }
}
class DataReq extends CacheReq{
    val mem:MemType = new MemType
    val wdata :UInt = LISA.GPR
    val wstrb :Bits = Bits( 4 bits)
    val cacop :Cacop = new Cacop
    def getSize:UInt = mem.size
    override def asMaster():Unit = {
        super.asMaster()
        out(mem,wdata,wstrb,cacop)
    }
}
class DataResp extends CacheResp
class DCacheCtrl extends CacheCtrl{
    import CacheState._
    val mem        = new MemType
    val req_cacop  = new Cacop
    val cacop      = new Cacop
    // 1st stage
    val accept = Bool()
    val req_st_0_tag = req_cacop.valid && req_cacop.isSt0Tag && req_cacop.isDCache
    val req_read_tag = !req_cacop.valid || req_cacop.isDCache && !req_cacop.isSt0Tag
    val req_read_tlb = !req_cacop.valid || req_cacop.isHitInv
    val read_tlb = Bool()
    val read_tag = Bool()
    val st_0_tag = Bool()
    // 2nd stage
    val read_dat = Bool()
    val update   = Bool()
    val inv_wrbk = Bool()
    val wrt_back = Bool()
    val send_cop = Bool()
    val icop_rdy = Bool()
    // 3rd stage
    val data_ok  = Reg(Bool())
    val replace  = Reg(Bool())
    val dirty    = Bool()

    val need_fill = cached
    val need_resp = mem.ld

    def acceptReq:Unit = {
        addr_ok  := !init.valid
    }
    def sendMiss:Unit = {
        miss_valid := True
    }
    update   := find_ok && mem.st
    read_dat := find_ok && mem.ld
    data_ok  := read_dat
    when(s === sOperate){
        assert(cacop.valid)
    }
    val is_my_op   = s === sOperate && cacop.isDCache
    val is_hit_inv = s === sOperate && cacop.isHitInv
    val my_hit_inv = cacop.isDCache && is_hit_inv
    val my_idx_inv = is_my_op && cacop.isIdxInv
    val do_my_op   = is_my_op   && miss_ready
    val do_hit_inv = my_hit_inv && miss_ready && !ex
    val do_idx_inv = my_idx_inv && miss_ready
    send_cop   := s === sOperate && cacop.isICache
    inv_wrbk := my_hit_inv || my_idx_inv
    wrt_back := send_miss && cached || inv_wrbk
    replace  := wrt_back
    read_tag := False
    read_tlb := False
    st_0_tag := False
    accept   := False
    when(init.valid){
        s := sIdle
    }.elsewhen(addr_ok){
        when(req_valid){
            accept := True
            st_0_tag := req_st_0_tag
            read_tag := req_read_tag
            read_tlb := req_read_tlb
            when(req_cacop.valid){
                when(req_st_0_tag){
                    s := sIdle
                }.otherwise{
                    s := sOperate
                }
            }.otherwise{
                s := sLookup
            }
        }.elsewhen(s =/= sIdle){
            s := sIdle
        }
    }.elsewhen(send_miss){
        s := Mux(cached || mem.ld, sMiss, sIdle)
    }.elsewhen(s === sOperate){
        when(is_hit_inv && ex){
            s := sIdle
        }.elsewhen(cacop.isDCache){
            when(miss_ready){
                s := sIdle
            }
        }.elsewhen(cacop.isICache){
            send_cop := !cacop.isHitInv || !ex
            when(icop_rdy){
                s := sIdle
            }
        }.otherwise{
            s := sIdle
        }
    }
    def collect(miss:DMiss):Unit = {
        miss_ready := miss.arready
        miss.arvalid := miss_valid &&(mem.ld || cached)
        miss.awvalid := ( miss_valid && mem.st && !cached
                       || replace && dirty)
        miss.occupy  := s === sLookup || is_my_op
    }
    def collect(info:DLookupInfo):Unit = {
        ex     := info.ex
        cached := info.cached
        mem    := info.mem
        cacop  := info.cacop
    }
    def collect(req:DataReq):Unit = {
        req_valid := req.valid
        req_cacop := req.cacop
        req.allow := addr_ok
    }
    def collect(icop:CacopBus){
        icop_rdy := icop.ready
        icop.valid := send_cop
    }
}

class DCache extends Component{
    val io = new Bundle{
        val dreq  :DataReq  = slave (new DataReq )
        val dexcpt:DExcept  = out   (new DExcept )
        val dresp :DataResp = master(new DataResp)
        val dsrch :DTlbSrch = master(new DTlbSrch)
        val dmiss :DMiss    = master(new DMiss   )
        val icop  :CacopBus = master(new CacopBus)
    }
    val cfg  = DCacheConfig
    val tagv = cfg.getTagV
    val data = cfg.getDDat
    val ctrl = new DCacheCtrl
    val info = new DLookupInfo
    val repl = new LRUReplace
    val recv = new BusReceiver(io.dresp,io.dmiss)
    val (hits,hit) = info.tagCompare(tagv)
    val buf_hits = Reg(hits)
    val buf_wpos = RegNextWhen(info.wpos,ctrl.read_dat)
    val buf_tagv = Reg(tagv(0).io.rd)
    val rtag = cfg.getVec1D(tagv(_).io.rd)
    val rdat = cfg.getVec2D(data(_)(_).io.rd)
    val is_dirty:Bits = cfg.getWayBits((i:Int)=>data(i).map(_.io.rb).reduce(_||_))
    val dat_line:Vec[UInt] = info.selectWay(buf_hits,rdat)
    val rval = dat_line(buf_wpos)
    val fill = new CacheFiller(io.dmiss)
    ctrl.dirty := (buf_hits & is_dirty).orR && buf_tagv.msb
    io.dsrch.va    := io.dreq.va
    io.dsrch.st    := io.dreq.mem.st
    io.dsrch.valid := ctrl.read_tlb
    ctrl.collect(io.dreq)
    ctrl.collect(io.dresp)
    ctrl.collect(io.dmiss)
    ctrl.collect(info)
    ctrl.collect(recv)
    ctrl.collect(fill)

    ctrl.collect(io.icop)
    io.icop.code := info.cacop.code
    io.icop.ptag := info.ptag
    io.icop.vidx := info.vidx

    ctrl.hit := hit
    info.va := io.dreq.va
    info.assignSomeByName(io.dsrch.res)
    when(ctrl.accept){
        info.vidx := info.nextVIdx
        info.mem  := io.dreq.mem
        info.wdata  := io.dreq.wdata
        info.wstrb  := io.dreq.wstrb
        info.cacop  := io.dreq.cacop
    }
    info.setFields(io.dmiss)
    io.dexcpt.ex   := (ctrl.find_ok || ctrl.is_hit_inv) && info.ex
    info.send_ex(io.dexcpt)
    io.dresp.valid := recv.valid || ctrl.data_ok
    io.dresp.data  := Mux(recv.valid,recv.data,rval)

    val bus_size = U(2, 3 bits)
    when(!info.cached){
        io.dmiss.arsize := info.mem.size
        io.dmiss.awsize := info.mem.size
        io.dmiss.awaddr := info.pa
        io.dmiss.wdata(0) := info.wdata
        for(i <- 1 until io.dmiss.wdata.length){
            io.dmiss.wdata(i) := U(0)
        }
        io.dmiss.wstrb  := info.wstrb
    }.otherwise{
        io.dmiss.arsize  := bus_size
        io.dmiss.awaddr  := buf_tagv(0, LISA.w_ppn bits) @@ info.widx @@ U(0, 4 bits)
        io.dmiss.awsize  := bus_size
        io.dmiss.wdata   := dat_line
        io.dmiss.wstrb.setAll()
    }
    val tagv_a = UInt(8 bits)
    val data_a = info.widx
    when(ctrl.init.valid){
        tagv_a := ctrl.init.idx
    }.elsewhen(fill.valid || ctrl.inv_wrbk){
        tagv_a := info.widx
    }.otherwise{
        tagv_a := info.ridx
    }
    val repl_mask = Mux(ctrl.inv_wrbk, B(0), cfg.getWayMask(repl.way))
    val hinv_mask = Mux(ctrl.my_hit_inv,hits,B(0))
    val iinv_mask = Mux(ctrl.my_idx_inv,cfg.getWayMask(info.vidx(0, 1 bits)),B(0))
    val wrbk_mask = hinv_mask | iinv_mask | repl_mask
    val sel_tagv  = MuxOH0((repl_mask | iinv_mask).asBools,rtag).asUInt
    val hit_tagv  = Mux(ctrl.my_hit_inv,hit.asUInt @@ info.ptag,U(0))
    val wrbk_tagv = sel_tagv | hit_tagv
    val fill_wpos = fill.getPos(info)
    val fill_wmsk = Range(0,cfg.n_bank).map(fill_wpos === U(_))
    val bank_mask = Range(0,cfg.n_bank).map(info.wpos === U(_))
    val st_0_mask = Range(0,cfg.n_bank).map(info.va(0 downto 0) === U(_))
    val fill_tagv = fill.isWriteTagV(io.dmiss)
    val fill_data = fill.isWriteData(io.dmiss)
    val tagv_wd:UInt = Mux(fill_tagv,info.tagv,U(0))
    val data_w_sel:Bool = info.mem.st && (fill.num === U(0) || !fill.valid)
    val data_ws:Bits = Mux(data_w_sel, info.wstrb, B(0))
    val data_wm:Bits = Mux(fill.valid,B"1111",info.wstrb)
    val data_wd:UInt = Range(3,-1,-1).map((i:Int)=>
        Mux(data_ws(i),info.wdata(i*8, 8 bits),io.dmiss.rdata(i*8, 8 bits))
    ).reduce(_ @@ _)
    cfg.each_way(tagv(_)   .io.wd := tagv_wd)
    cfg.each_way(tagv(_)   .io.a  := tagv_a )
    cfg.each_blk(data(_)(_).io.wd := data_wd)
    cfg.each_blk(data(_)(_).io.wm := data_wm)
    cfg.each_blk(data(_)(_).io.wb := data_w_sel)
    cfg.each_blk(data(_)(_).io.a  := data_a )
    when(ctrl.init.valid){
        cfg.each_way(tagv(_).io.setWrite())
    }.elsewhen(fill_tagv){
        cfg.each_way((i:Int)=>tagv(i).io.setWriteWhen(repl_mask(i)))
    }.elsewhen(ctrl.inv_wrbk){
        cfg.each_way((i:Int)=>tagv(i).io.setWriteWhen(wrbk_mask(i)))
    }.elsewhen(ctrl.read_tag){
        cfg.each_way((i:Int)=>tagv(i).io.setRead())
    }.elsewhen(ctrl.st_0_tag){
        cfg.each_way((i:Int)=>tagv(i).io.setWriteWhen(st_0_mask(i)))
    }.otherwise{
        cfg.each_way(tagv(_).io.setIdle())
    }
    when(fill_data){
        cfg.each_blk((i:Int,j:Int)=>
            data(i)(j).io.setWriteWhen(repl_mask(i) && fill_wmsk(j))
        )
    }.elsewhen(ctrl.update){
        cfg.each_blk((i:Int,j:Int)=>
            data(i)(j).io.setWriteWhen(hits(i) && bank_mask(j))
        )
    }.elsewhen(ctrl.read_dat){
        cfg.each_blk((i:Int,j:Int)=>
            data(i)(j).io.setReadWhen(hits(i) && bank_mask(j))
        )
    }.elsewhen(ctrl.wrt_back){
        cfg.each_blk((i:Int,j:Int)=>
            data(i)(j).io.setReadWhen(wrbk_mask(i))
        )
    }.otherwise{
        cfg.each_blk((i:Int,j:Int)=>
            data(i)(j).io.setIdle()
        )
    }
    when(ctrl.read_dat){
        buf_hits := hits
    }.elsewhen(ctrl.wrt_back){
        buf_tagv := wrbk_tagv
        buf_hits := wrbk_mask
    }
    when(ctrl.read_tag){
        repl.read(info.ridx)
    }
    when(ctrl.init.valid){
        repl.write(ctrl.init.idx,False)
    }.elsewhen(io.dresp.valid && io.dresp.allow){
        repl.write(info.widx,repl.update(fill.valid,buf_hits))
    }
}
